import cv2
import numpy as np
import tkinter as tk
from tkinter import messagebox

class CameraViewer:
    def __init__(self):
        self.cameras = self._get_available_cameras()
        # 检查是否有可用摄像头
        if not self.cameras or len(self.cameras) == 0:
            # 使用tkinter显示错误消息
            root = tk.Tk()
            root.withdraw()  # 隐藏主窗口
            messagebox.showerror("错误", "未检测到可用的摄像头设备！")
            root.destroy()
            exit(1)
        
        self.current_camera = 0
        self.cap = None
        self.drawing = False
        self.ix, self.iy = -1, -1
        self.fx, self.fy = -1, -1
        self.original_frame = None
        self.zoomed = False
        self.window_name = "Camera Viewer"
        
    def _get_available_cameras(self):
        cameras = []
        for i in range(5):  # 检查最多5个摄像头
            cap = cv2.VideoCapture(i)
            if cap.isOpened():
                cameras.append(i)
                cap.release()
        # 不再返回默认值[0]，让初始化方法处理空列表情况
        return cameras
    
    def _mouse_callback(self, event, x, y, flags, param):
        # 获取当前显示图像的实际尺寸
        window_w = cv2.getWindowImageRect(self.window_name)[2]
        window_h = cv2.getWindowImageRect(self.window_name)[3] - 50  # 减去控制面板高度
        
        # 计算缩放比例
        scale = min(window_h/self.original_frame.shape[0], window_w/self.original_frame.shape[1])
        new_w = int(self.original_frame.shape[1] * scale)
        new_h = int(self.original_frame.shape[0] * scale)
        
        # 计算填充偏移
        pad_w = (window_w - new_w) // 2
        pad_h = (window_h - new_h) // 2
        
        # 转换鼠标坐标到图像坐标
        if x >= pad_w and x < pad_w + new_w and y >= pad_h and y < pad_h + new_h:
            if self.zoomed:
                # 在缩放状态下，基于当前显示的缩放图像计算坐标
                disp_x = int((x - pad_w) * (self.zoomed_frame.shape[1] / new_w))
                disp_y = int((y - pad_h) * (self.zoomed_frame.shape[0] / new_h))
                # 将坐标映射回原始图像中的区域
                x1, x2 = sorted([self.prev_ix, self.prev_fx])
                y1, y2 = sorted([self.prev_iy, self.prev_fy])
                orig_x = x1 + int(disp_x * (x2 - x1) / self.zoomed_frame.shape[1])
                orig_y = y1 + int(disp_y * (y2 - y1) / self.zoomed_frame.shape[0])
            else:
                orig_x = int((x - pad_w) / scale)
                orig_y = int((y - pad_h) / scale)
        else:
            orig_x, orig_y = -1, -1
        
        if y >= window_h:  # 点击在控制面板区域
            if event == cv2.EVENT_LBUTTONDOWN:
                # 检查是否点击了摄像头选择按钮
                for i, cam in enumerate(self.cameras):
                    # 相应地调整点击区域的起始位置为130
                    if 130 + i*30 <= x <= 150 + i*30:
                        self.current_camera = cam
                        self.zoomed = False
                        if self.cap:
                            self.cap.release()
                        self.cap = cv2.VideoCapture(self.current_camera)
                        return
                
                # 检查是否点击了Exit按钮
                if x >= window_w-100 and x <= window_w-50:
                    cv2.destroyAllWindows()
                    self.cap.release()
                    exit(0)
        else:  # 点击在图像区域
            if orig_x != -1 and orig_y != -1:
                if event == cv2.EVENT_LBUTTONDOWN:
                    self.drawing = True
                    self.ix, self.iy = orig_x, orig_y
                    self.fx, self.fy = orig_x, orig_y
                elif event == cv2.EVENT_MOUSEMOVE:
                    if self.drawing:
                        self.fx, self.fy = orig_x, orig_y
                elif event == cv2.EVENT_LBUTTONUP:
                    self.drawing = False
                    self.fx, self.fy = orig_x, orig_y
                    if abs(self.ix - self.fx) > 10 and abs(self.iy - self.fy) > 10:
                        # 保存上一次的选择区域坐标
                        self.prev_ix, self.prev_iy = self.ix, self.iy
                        self.prev_fx, self.prev_fy = self.fx, self.fy
                        self._zoom_region()
                elif event == cv2.EVENT_LBUTTONDBLCLK:
                    self.zoomed = False
    
    def _zoom_region(self):
        if self.original_frame is None:
            return
            
        x1, x2 = sorted([self.ix, self.fx])
        y1, y2 = sorted([self.iy, self.fy])
        
        # 确保坐标在图像范围内
        h, w = self.original_frame.shape[:2]
        x1, y1 = max(0, x1), max(0, y1)
        x2, y2 = min(w, x2), min(h, y2)
        
        if x2 > x1 and y2 > y1:
            # 获取框选区域
            selected_region = self.original_frame[y1:y2, x1:x2]
            # 将框选区域放大到原始图像大小
            self.zoomed_frame = cv2.resize(selected_region, (w, h), interpolation=cv2.INTER_LINEAR)
            self.zoomed = True
    
    def _create_control_panel(self, frame):
        control_panel = np.zeros((50, frame.shape[1], 3), dtype=np.uint8)
        
        # 添加摄像头选择按钮，增加文本和按钮之间的间距
        cv2.putText(control_panel, "Cameras:", (10, 30), 
                   cv2.FONT_HERSHEY_SIMPLEX, 0.7, (255, 255, 255), 1)
        
        # 将数字按钮的起始位置从100改为130，增加更多间距
        for i, cam in enumerate(self.cameras):
            color = (0, 255, 0) if cam == self.current_camera else (255, 255, 255)
            cv2.putText(control_panel, f"{i}", (130 + i*30, 30), 
                       cv2.FONT_HERSHEY_SIMPLEX, 0.7, color, 1)
        
        # 添加退出按钮
        cv2.putText(control_panel, "Exit", (frame.shape[1]-100, 30), 
                   cv2.FONT_HERSHEY_SIMPLEX, 0.7, (0, 0, 255), 1)
        
        return control_panel
    
    def _handle_click(self, x, y, frame_width):
        # 检查是否点击了摄像头选择按钮
        if y < 50:  # 控制面板区域
            for i, cam in enumerate(self.cameras):
                if 100 + i*30 <= x <= 120 + i*30:
                    self.current_camera = cam
                    self.zoomed = False
                    if self.cap:
                        self.cap.release()
                    self.cap = cv2.VideoCapture(self.current_camera)
                    return True
            
            # 检查是否点击了退出按钮
            if frame_width-100 <= x <= frame_width-50:
                return False
        
        return True
    
    def run(self):
        cv2.namedWindow(self.window_name, cv2.WINDOW_NORMAL)  # 改为可调整大小的窗口
        
        cv2.setMouseCallback(self.window_name, self._mouse_callback)
        
        # 再次检查摄像头是否可用
        self.cap = cv2.VideoCapture(self.current_camera)
        if not self.cap.isOpened():
            # 使用tkinter显示错误消息
            root = tk.Tk()
            root.withdraw()  # 隐藏主窗口
            messagebox.showerror("错误", f"无法打开摄像头 {self.current_camera}！")
            root.destroy()
            return
        
        running = True
        while running:
            ret, frame = self.cap.read()
            if not ret:
                # 显示错误消息
                root = tk.Tk()
                root.withdraw()
                messagebox.showerror("错误", "无法读取摄像头画面！")
                root.destroy()
                break
                
            self.original_frame = frame.copy()
            
            # 获取当前窗口尺寸
            window_w = cv2.getWindowImageRect(self.window_name)[2]
            window_h = cv2.getWindowImageRect(self.window_name)[3] - 50  # 减去控制面板高度
            
            # 计算保持比例的缩放因子
            scale = min(window_h/self.original_frame.shape[0], window_w/self.original_frame.shape[1])
            new_w = int(self.original_frame.shape[1] * scale)
            new_h = int(self.original_frame.shape[0] * scale)
            
            # 计算填充区域（居中显示）
            pad_w = (window_w - new_w) // 2
            pad_h = (window_h - new_h) // 2
            
            # 创建带填充的显示图像
            display_frame = np.zeros((window_h, window_w, 3), dtype=np.uint8)
            resized = cv2.resize(
                self.zoomed_frame if self.zoomed else self.original_frame,
                (new_w, new_h), 
                interpolation=cv2.INTER_LINEAR
            )
            display_frame[pad_h:pad_h+new_h, pad_w:pad_w+new_w] = resized
            
            # 绘制选择框 - 修改条件判断
            if self.drawing:  # 移除对self.zoomed的检查
                # 计算显示尺寸上的坐标（考虑填充偏移）
                disp_ix = int(self.ix * scale) + pad_w
                disp_iy = int(self.iy * scale) + pad_h
                disp_fx = int(self.fx * scale) + pad_w
                disp_fy = int(self.fy * scale) + pad_h
                cv2.rectangle(display_frame, (disp_ix, disp_iy), (disp_fx, disp_fy), (0, 255, 0), 2)
            
            # 创建控制面板并合并显示
            control_panel = self._create_control_panel(display_frame)
            combined = np.vstack([display_frame, control_panel])
            
            cv2.imshow(self.window_name, combined)
            
            # 处理键盘输入
            key = cv2.waitKey(1)
            if key == 27:  # ESC键
                break
                
            # 处理窗口关闭事件
            if cv2.getWindowProperty(self.window_name, cv2.WND_PROP_VISIBLE) < 1:
                break
                
            # 完全移除以下三行代码
            # mouse_x = cv2.getWindowImageRect(self.window_name)[2] - cv2.getMousePos()[0]
            # mouse_y = cv2.getMousePos()[1]
            # if cv2.getMousePos()[2] == 1:  # 鼠标左键点击
            #     running = self._handle_click(mouse_x, mouse_y, display_frame.shape[1])
        
        self.cap.release()
        cv2.destroyAllWindows()

if __name__ == "__main__":
    viewer = CameraViewer()
    viewer.run()